function validateName(name) {
    return !name.toString().match(/[^a-z_0-9]/ig);
}

// serialize the database
function serialize(db) {
    return JSON.stringify(db);
}

// throw an error
function error(msg) {
    throw new Error(msg);
}

// return all the IDs in a table
function getIDs(db, table_name) {
    let result_ids = [];

    for(let ID in db.data[table_name]) {
        if( db.data[table_name].hasOwnProperty(ID) ) {
            result_ids.push(ID);
        }
    }
    return result_ids;
}

// clone an object
function clone(obj) {
    let new_obj = {};
    for(let key in obj) {
        if( obj.hasOwnProperty(key) ) {
            new_obj[key] = obj[key];
        }
    }
    return new_obj;
}

// commit the database to localStorage
function commit(storage, db_id, db) {
    try {
        storage.setItem(db_id, db);
        return true;
    } catch(e) {
        return false;
    }
}

// create a table
function createTable(db, table_name, fields) {
    db.tables[table_name] = {fields: fields, auto_increment: 1};
    db.data[table_name] = {};
}

// drop a table
function dropTable(db, table_name) {
    delete db.tables[table_name];
    delete db.data[table_name];
}

// check whether a table exists
function tableExists(db, table_name) {
    return db.tables[table_name] ? true : false;
}

// delete rows, given a list of their IDs in a table
function deleteRows(db, table_name, ids) {
    for(let i=0; i<ids.length; i++) {
        if( db.data[table_name].hasOwnProperty(ids[i]) ) {
            delete db.data[table_name][ ids[i] ];
        }
    }
    return ids.length;
}

// select rows, given a list of IDs of rows in a table
function select(db, table_name, ids, start, limit, sort, distinct) {
    let ID = null, results = [], row = null;

    for(let i=0; i<ids.length; i++) {
        ID = ids[i];
        row = db.data[table_name][ID];
        results.push( clone(row) );
    }

    // there are sorting params
    // if(Array.isArray(sort)) {
    //     for(let i=0; i<sort.length; i++) {
    //         results.sort(sort_results(sort[i][0], sort[i].length > 1 ? sort[i][1] : null));
    //     }
    // }

    // distinct params
    // if(Array.isArray(distinct)) {
    //     for(let j=0; j<distinct.length; j++) {
    //         let seen = {}, d = distinct[j];
    //
    //         for(let i=0; i<results.length; i++) {
    //             if(results[i] === undefined) {
    //                 continue;
    //             }
    //
    //             if(results[i].hasOwnProperty(d) && seen.hasOwnProperty(results[i][d])) {
    //                 delete(results[i]);
    //             } else {
    //                 seen[results[i][d]] = 1;
    //             }
    //         }
    //     }
    //
    //     // can't use .filter(ie8)
    //     let new_results = [];
    //     for(let i=0; i<results.length; i++) {
    //         if(results[i] !== undefined) {
    //             new_results.push(results[i]);
    //         }
    //     }
    //
    //     results = new_results;
    // }

    // limit and offset
    start = start && typeof start === "number" ? start : null;
    limit = limit && typeof limit === "number" ? limit : null;

    if(start && limit) {
        results = results.slice(start, start+limit);
    } else if(start) {
        results = results.slice(start);
    } else if(limit) {
        results = results.slice(start, limit);
    }

    return results;
}

// check whether a table exists, and if not, throw an error
function tableExistsWarn(db, table_name) {
    if(!tableExists(db, table_name)) {
        error("The table '" + table_name + "' does not exist");
    }
}

export class LocalStorageDB {
    constructor(db_name, engine) {
        this.db_name = db_name;
        this.db_prefix = 'db_';
        this.db_id = this.db_prefix + this.db_name;
        this.db_new = false;	// this flag determines whether a new database was created during an object initialisation
        this.db = null;
        this.storage = engine ? engine : LocalStorage;
        this.db = this.__createDb(this.db_name);
    }
    __createDb(db_name) {
        let self = this;
        let db = this.storage.getItem(self.db_id);
        // if (db && db.tables && db.data) {
        //     console.log("db created");
        // }
        
        // if the database doesn't exist, create it
        if( !( db && db.tables && db.data ) ) {
            console.log("db first created");
            if(!validateName(db_name)) {
                error("The name '" + db_name + "' contains invalid characters");
            } else {
                db = {tables: {}, data: {}};
                commit(self.storage, self.db_id, db);
                self.db_new = true;
            }
        }
        return db;
    }
    // check whether a table exists
    tableExists(table_name) {
        let self = this;
        return tableExists(self.db, table_name);
    }
    // create a table
    createTable(table_name, fields) {
        let self = this;
        let result = false;
        if(!validateName(table_name)) {
            error("The database name '" + table_name + "' contains invalid characters.");
        } else if(self.tableExists(table_name)) {
            error("The table name '" + table_name + "' already exists.");
        } else {
            // make sure field names are valid
            let is_valid = true;
            let i;
            for(i =0; i<fields.length; i++) {
                if(!validateName(fields[i])) {
                    is_valid = false;
                    break;
                }
            }

            if(is_valid) {
                // cannot use indexOf due to <IE9 incompatibility
                // de-duplicate the field list
                let fields_literal = {};
                for(i=0; i<fields.length; i++) {
                    fields_literal[ fields[i] ] = true;
                }
                delete fields_literal['ID']; // ID is a reserved field name

                fields = ['ID'];
                for(let field in fields_literal) {
                    if( fields_literal.hasOwnProperty(field) ) {
                        fields.push(field);
                    }
                }

                createTable(self.db, table_name, fields);
                result = true;
            } else {
                error("One or more field names in the table definition contains invalid characters");
            }
        }

        return result;
    }
    createTableIfNotExists(table_name, fields) {
        let self = this;
        // console.log(self.tableExists(table_name));
        if (self.tableExists(table_name)) {
            // console.log("tableExists");
            return;
        } else {
            self.createTable(table_name, fields);
        }
    }
    // drop a table
    dropTable(table_name) {
        let self = this;
        tableExistsWarn(self.db, table_name);
        dropTable(self.db, table_name);
    }
    // insert a new row
    insert(table_name, data) {
        let self = this;
        let db = self.db;
        data.ID = db.tables[table_name].auto_increment;
        db.data[table_name][ db.tables[table_name].auto_increment ] = data;
        db.tables[table_name].auto_increment++;
        return data.ID;
    }
    // select rows
    query(table_name, query, limit, start, sort, distinct) {
        let self = this;
        tableExistsWarn(self.db, table_name);

        let result_ids = [];
        if(!query) {
            result_ids = getIDs(self.db, table_name); // no conditions given, return all records
        } else if(typeof query === 'object') {			// the query has key-value pairs provided
            // result_ids = queryByValues(table_name, validFields(table_name, query), limit, start);
        } else if(typeof query === 'function') {		// the query has a conditional map function provided
            // result_ids = queryByFunction(table_name, query, limit, start);
        }

        return select(self.db, table_name, result_ids, start, limit, sort, distinct);
    }
    queryAll(table_name, params) {
        if(!params) {
            return this.query(table_name)
        } else {
            return this.query(table_name,
                params.hasOwnProperty('query') ? params.query : null,
                params.hasOwnProperty('limit') ? params.limit : null,
                params.hasOwnProperty('start') ? params.start : null,
                params.hasOwnProperty('sort') ? params.sort : null,
                params.hasOwnProperty('distinct') ? params.distinct : null
            );
        }
    }
    // delete rows
    deleteRows(table_name, query) {
        let self = this;
        tableExistsWarn(self.db, table_name);

        let result_ids = [];
        if(!query) {
            result_ids = getIDs(self.db, table_name);
        } else if(typeof query === 'object') {
            // result_ids = queryByValues(table_name, validFields(table_name, query));
        } else if(typeof query === 'function') {
            // result_ids = queryByFunction(table_name, query);
        }
        return deleteRows(self.db, table_name, result_ids);
    }
    getTables() {
        return this.db.tables;
    }
    getTableNames() {
        return Object.keys(this.db.tables);
    }
    commit() {
        let self = this;
        return commit(self.storage, self.db_id, self.db);
    }
}

